#pragma once
#include <iostream>
#include <memory>
#include <string>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>     // memeset
#include <functional>
#include "log.hpp"
using namespace std;

const string defaultIP = "0.0.0.0";    // 默认IP地址
const uint16_t defaultPort = 8080;     // 默认端口号
const size_t BUFFERSIZE = 1024;
Log log;    // 方便打日志
using func_t = function<string(const string &)>;

enum {
    SOCKET_ERR,
    BIND_ERR,
};

class UdpServer
{
public:
    UdpServer(const uint16_t& port, const string& ip);
    ~UdpServer();
    void init();
    void run(func_t fun);
private:
    int _socketFd = 0;      
    string _ip;
    uint16_t _port;     // 服务器进程端口号
    bool _isRunning = false;    
};

UdpServer::UdpServer(const uint16_t& port = defaultPort, const string& ip = defaultIP) : _port(port), _ip(ip)
{}

UdpServer::~UdpServer()
{
    close(_socketFd);
}

void UdpServer::init()
{
    /* 1.创建udp套接字 */ 
    _socketFd = socket(AF_INET, SOCK_DGRAM, 0);        
    if(_socketFd == -1) {
        // 创建失败
        log(FATAL, "创建套接字失败, fd: %d, 原因: %s\n", _socketFd, strerror(errno));
        exit(SOCKET_ERR);
    }
    else {
        // 创建成功
        log(INFO, "创建套接字成功, fd: %d\n", _socketFd);
        /* 2. 将一个套接字绑定到一个特定的网络地址 */
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        in_addr_t ipTmp = inet_addr(_ip.c_str());
        // sin_addr是个in_addr结构体类型的变量，里面有个in_addr_t类型的成员s_addr
        // local.sin_addr.s_addr = htons(INADDR_ANY);
        local.sin_addr.s_addr = ipTmp;
        // 开始绑定
        if(bind(_socketFd, (const struct sockaddr*)&local, sizeof(local)) == -1) {
            log(FATAL, "绑定失败, 原因: %s\n", strerror(errno));
            exit(BIND_ERR);
        }
        log(INFO, "绑定成功\n");
    }
}

void UdpServer::run(func_t fun)
{
    /* 从客户端拿数据，处理后再发给客户端 */
    _isRunning = true;
    while (_isRunning) {
        // 接受数据
        sockaddr_in client;
        socklen_t len = sizeof(client);  
        string inBuffer(BUFFERSIZE, 0);
        if(recvfrom(_socketFd, (char*)inBuffer.c_str(), inBuffer.size(), 0, (sockaddr*)&client, &len) == -1) {
            log(WARNING, "接受数据失败, 原因: %s\n", strerror(errno));
        }
        cout << inBuffer << '\n';
        // 服务端进行数据处理
        string echoString = fun(inBuffer);      // 回调fun函数
        // 发回客户端
        if (sendto(_socketFd, echoString.c_str(), echoString.size(), 0, (const sockaddr*)&client, len) == -1) {
            log(WARNING, "发送数据失败, 原因: %s\n", strerror(errno));
        }
    }
}

